/*-------------------------------------------------------------------------
 *
 * copydir.c
 *	  复制目录
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	虽然 "xcopy /e /i /q" 在复制目录时正常工作，但在 Windows XP 上
 *	它需要一个窗口句柄，这会阻止它在作为服务调用时工作。
 *
 * IDENTIFICATION
 *	  src/backend/storage/file/copydir.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#include "miscadmin.h"
#include "pgstat.h"
#include "storage/copydir.h"
#include "storage/fd.h"

/* 
 * copydir: 复制目录
 *
 * 如果递归为false，则忽略子目录。 任何不是目录或常规文件的内容都将被忽略。
 */
void copydir(char *fc_fromdir, char *fc_todir, bool fc_recurse)
{
	DIR		   *fc_xldir;
	struct dirent *fc_xlde;
	char		fc_fromfile[MAXPGPATH * 2];
	char		fc_tofile[MAXPGPATH * 2];

	if (MakePGDirectory(fc_todir) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create directory \"%s\": %m", fc_todir)));

	fc_xldir = AllocateDir(fc_fromdir);

	while ((fc_xlde = ReadDir(fc_xldir, fc_fromdir)) != NULL)
	{
		struct stat fc_fst;

		/* 如果在复制目录期间收到取消信号，则退出 */
		CHECK_FOR_INTERRUPTS();

		if (strcmp(fc_xlde->d_name, ".") == 0 ||
			strcmp(fc_xlde->d_name, "..") == 0)
			continue;

		snprintf(fc_fromfile, sizeof(fc_fromfile), "%s/%s", fc_fromdir, fc_xlde->d_name);
		snprintf(fc_tofile, sizeof(fc_tofile), "%s/%s", fc_todir, fc_xlde->d_name);

		if (lstat(fc_fromfile, &fc_fst) < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_fromfile)));

		if (S_ISDIR(fc_fst.st_mode))
		{
			/* 递归处理子目录 */
			if (fc_recurse)
				copydir(fc_fromfile, fc_tofile, true);
		}
		else if (S_ISREG(fc_fst.st_mode))
			copy_file(fc_fromfile, fc_tofile);
	}
	FreeDir(fc_xldir);

	/* 
	 * 在这里要小心，并对所有文件进行fsync，以确保复制确实完成。 但如果fsync被禁用，我们就完成了。
	 */
	if (!enableFsync)
		return;

	fc_xldir = AllocateDir(fc_todir);

	while ((fc_xlde = ReadDir(fc_xldir, fc_todir)) != NULL)
	{
		struct stat fc_fst;

		if (strcmp(fc_xlde->d_name, ".") == 0 ||
			strcmp(fc_xlde->d_name, "..") == 0)
			continue;

		snprintf(fc_tofile, sizeof(fc_tofile), "%s/%s", fc_todir, fc_xlde->d_name);

		/* 
		 * 我们在这里不需要同步子目录，因为递归的copydir将在返回之前完成。
		 */
		if (lstat(fc_tofile, &fc_fst) < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_tofile)));

		if (S_ISREG(fc_fst.st_mode))
			fsync_fname(fc_tofile, false);
	}
	FreeDir(fc_xldir);

	/* 
	 * 确保同步目标目录本身是重要的，因为单个文件fsync并不能保证该文件的目录条目已同步。 最近的ext4版本已经使窗口宽得多，但过去在ext3和其他文件系统中也是如此。
	 */
	fsync_fname(fc_todir, true);
}

/* 
 * 复制一个文件 
 */
void copy_file(char *fc_fromfile, char *fc_tofile)
{
	char	   *fc_buffer;
	int			fc_srcfd;
	int			fc_dstfd;
	int			fc_nbytes;
	off_t		fc_offset;
	off_t		fc_flush_offset;

	/* 复制缓冲区的大小（读取和写入请求） */
#define COPY_BUF_SIZE (8 * BLCKSZ)

	/* 
	 * 数据刷新请求的大小。 在大多数平台上，将其每1MB左右执行似乎是有益的。 但macOS，至少在早期版本的APFS中，对小的mmap/msync请求确实不友好，因此在那里仅每32MB执行一次。
	 */
#if defined(__darwin__)
#define FLUSH_DISTANCE (32 * 1024 * 1024)
#else
#define FLUSH_DISTANCE (1024 * 1024)
#endif

	/* 使用palloc以确保我们获得最大对齐的缓冲区 */
	fc_buffer = palloc(COPY_BUF_SIZE);

	/* 
	 * 打开文件 
	 */
	fc_srcfd = OpenTransientFile(fc_fromfile, O_RDONLY | PG_BINARY);
	if (fc_srcfd < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_fromfile)));

	fc_dstfd = OpenTransientFile(fc_tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
	if (fc_dstfd < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m", fc_tofile)));

	/*
	 * 进行数据复制。
	 */
	fc_flush_offset = 0;
	for (fc_offset = 0;; fc_offset += fc_nbytes)
	{
		/* 如果在复制文件期间收到取消信号，则退出 */
		CHECK_FOR_INTERRUPTS();

		/*
 * 我们稍后会 fsync 这些文件，但在复制过程中，定期刷新它们以避免在缓存中产生垃圾，盼望内核能在 fsync 来临之前开始将它们写出。
 */
		if (fc_offset - fc_flush_offset >= FLUSH_DISTANCE)
		{
			pg_flush_data(fc_dstfd, fc_flush_offset, fc_offset - fc_flush_offset);
			fc_flush_offset = fc_offset;
		}

		pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
		fc_nbytes = read(fc_srcfd, fc_buffer, COPY_BUF_SIZE);
		pgstat_report_wait_end();
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m", fc_fromfile)));
		if (fc_nbytes == 0)
			break;
		errno = 0;
		pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
		if ((int) write(fc_dstfd, fc_buffer, fc_nbytes) != fc_nbytes)
		{
			/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
			if (errno == 0)
				errno = ENOSPC;
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write to file \"%s\": %m", fc_tofile)));
		}
		pgstat_report_wait_end();
	}

	if (fc_offset > fc_flush_offset)
		pg_flush_data(fc_dstfd, fc_flush_offset, fc_offset - fc_flush_offset);

	if (CloseTransientFile(fc_dstfd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_tofile)));

	if (CloseTransientFile(fc_srcfd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_fromfile)));

	pfree(fc_buffer);
}
